Skip to content

大家好,我是大志。

本篇文章我结合自己的学习和项目经验,把工具调用相关的高频面试题整理成了一份比较完整的总结。不仅会介绍每个知识点是什么,还会结合实际开发场景,聊聊生产环境中常见的设计思路,希望能够帮助大家建立更加系统的知识体系。

另外,完整的 AI Agent 面试题文档也已经同步到了 aiflowline.cn,大家可以结合文章一起阅读。

1、什么是Function Calling

Function Calling(函数调用)是LLM所具备的一种能力,函数调用是指模型不仅可以生成文本回答,还可以根据用户的提问,判断是否需要使用提供的工具,如需工具,就自动生成结构化参数,将工具调用信息返回给Agent程序,让Agent程序去调用外部工具(如外部API、数据库、搜索引擎等),最终将工具调用结果再返回给LLM来,LLM根据工具调用结果来判断是否还需要继续调用工具,还是直接返回结果。

image-20250821235102563

工具调用的核心思想是让大模型不只是能回答问题,还能根据问题和提供的工具来决定要不要调用工具、调用哪个工具,以及如何传参,从而完成更多的任务。

例如用户问“上海今天的天气怎么样”,LLM的训练数据肯定不包括这类实时信息,LLM可能会直接编造答案,但是有了工具调用,LLM就会判断需要调用如get_weather工具,并生成类似 get_weather(city="上海") 这样的函数调用参数,返回给Agent执行工具。

Function Calling 的本质,其实是大模型与外部系统之间的一种结构化通信方式,让 LLM 从聊天机器人变成”可以使用工具的Agent智能体”,通过工具调用可以解决LLM无法获取实时信息、无法执行外部操作的问题。

2、如何设计Tool Schema

Tool Schema 的核心目标是LLM能够准确理解工具的用途、用法,并正确生成调用参数。因此,一个好的Tool Schema不仅是给程序看的,更重要的是给LLM看的。

在设计 Tool Schema 时,最重要的是写好 namedescriptionparameters。其中模型最依赖的其实是 description,因为模型并不知道我们的代码逻辑,它只能通过描述来理解这个工具是干什么的。除此之外,参数的设计也要尽量清晰、具体,避免让模型猜测。

在设计工具和Tool Schema时,要遵循单一职责原则,一个 Tool 最好只完成一类任务。

例如:

python
get_weather(city)
search_news(keyword)

而不是做一个万能工具,这会增加模型选择和参数生成的难度,降低调用成功率。

python
query_data(
    type,
    city,
    keyword,
    category,
    ...
)

工具名称也应该尽可能清晰明了,如果多个工具功能相似,需要在 Description 中明确区分边界。

例如:search_websearch_internal_docs,描述中要明确说明:一个搜索互联网,一个搜索企业知识库,否则LLM容易选错工具。

3、工具调用如何做参数校验?

参数校验的目的是确保 Agent 生成的工具调用参数符合要求,避免因为参数错误导致工具调用失败。

虽然 Function Calling 会根据 Tool Schema 生成参数,但大模型并不能保证生成的参数一定正确。例如用户输入异常、模型理解错误或者参数缺失时,都可能导致工具调用失败。因此在工具真正执行之前,必须进行参数校验。

LangChain为例,通常会使用 Pydantic 定义参数模型:

python
from pydantic import BaseModel, Field

class WeatherInput(BaseModel):
    city: str = Field(
        description="需要查询天气的城市名称"
    )

然后绑定到 Tool

python
@tool(args_schema=WeatherInput)
def get_weather(city: str):
    return f"{city}天气晴朗"

当模型生成参数后:

python
{
    "city": "北京"
}

会先经过 Pydantic 校验,通过后才会执行工具。

如果模型生成:

json
{
    "city": 123
}

或者:

json
{}

则会抛出异常,不会继续执行工具。除了类型校验之外,实际项目中还经常会做业务规则校验。例如:

python
from pydantic import field_validator

class WeatherInput(BaseModel):
    city: str

    @field_validator("city")
    @classmethod
    def validate_city(cls, value):
        if not value.strip():
            raise ValueError("城市不能为空")
        return value

这样即使模型生成的city参数是空字符串,参数也会校验不通过。

4、工具调用失败如何处理?

Agent 应用中,工具调用失败非常常见,例如网络超时、API 限流、参数错误、数据库异常等。因此,工具调用需要设计完善的异常处理机制。

首先,要在 Tool 内部捕获异常,避免因为一个工具执行失败导致整个执行流程崩溃,让Agent仍然能够获取错误信息并继续运行。

python
@tool
def get_weather(city: str):
    try:
        return weather_api.query(city)
    except Exception as e:
        return f"天气查询失败:{str(e)}"

对于临时性故障,例如网络抖动、接口超时等问题,可以增加重试机制。

python
from tenacity import retry

@retry(stop=stop_after_attempt(3))
def get_weather(city: str):
    ...

Agent应用中,更推荐把错误信息返回给模型,而不是直接终止流程,模型收到错误后可以自主决策,比如返回给用户:天气查询服务暂时不可用,请稍后重试。

在复杂工作流中,还可以设计降级(Fallback)策略,例如主搜索工具Google Serper失败时,采用Bing Search继续进行搜索。

在生产环境中,还需要记录日志和监控,记录调用了哪个工具、输入参数是什么、错误原因是什么、重试次数是多少,这样方便后续排查问题和优化。

5、如何设计超时机制?

Agent 应用中,工具调用通常依赖第三方 API、数据库或搜索服务,这些服务可能出现请求超时的情况。因此需要设计超时机制,避免某个工具调用超时阻塞整个 Agent 流程。

最简单的做法是在调用外部服务时设置请求超时时间。例如使用 requests 调用接口时:

python
response = requests.get(
    url,
    timeout=10
)

如果 10 秒内没有返回结果,则抛出超时异常,交给模型去处理。

python
@tool
def get_weather(city: str):
    try:
        return weather_api.query(city)
    except TimeoutError:
        return "天气服务请求超时"

模型获取到错误信息,并决定后续如何处理。

在一些复杂场景下,经常会使用降级策略。当主工具超时时,可以切换到备用工具,这样能够提升整体成功率。

6、如何设计重试机制?

Agent 应用中,工具调用失败并不一定意味着真正的业务失败,很多时候是因为网络抖动、接口超时、服务限流等原因。因此通常会设计重试机制,在失败后自动重新执行,来提高工具调用成功率。

最简单的方式是在 Tool 调用失败后重试固定次数。例如:

python
from tenacity import retry, stop_after_attempt

@retry(stop=stop_after_attempt(3))
def get_weather(city: str):
    return weather_api.query(city)

当调用失败时,系统会自动重试,最多执行 3 次。

实际项目中,更推荐使用**指数退避(Exponential Backoff)**策略,而不是立即重试。

例如:

第1次失败 → 等待1秒
第2次失败 → 等待2秒
第3次失败 → 等待4秒

这样可以避免在服务异常时持续发送大量请求,增加系统压力。而且,并不是所有错误都适合重试,需要区分错误类型。对于网络超时、服务暂时不可用、API 限流(429)、数据库连接失败可以尝试重试。

而对于参数错误、权限不足、资源不存在、数据格式错误这类错误,无需进行重试,大概率重试之后也不会成功。

并且重试也要控制重试次数,如最大重试次数3~5次,超过次数后直接返回错误信息,否则可能陷入死循环。

7、什么是指数退避重试?

指数退避(Exponential Backoff)是一种重试策略,每次重试的等待时间呈指数增长。

如:

第1次重试:等待 1秒
第2次重试:等待 2秒
第3次重试:等待 4秒
第4次重试:等待 8秒
第5次重试:等待 16秒

等待时间 = 基础等待时间 * 2^(重试次数-1)

那么,为什么需要指数退避呢?如果服务端出了问题,大量客户端同时重试会导致服务端压力非常大,指数退避让客户端的重试间隔越来越长,能给服务端恢复的时间。

具体实现如下:

python
import time
import random

def execute_with_backoff(tool_name, arguments, max_retries=5, base_delay=1):
    for attempt in range(max_retries):
        try:
            return execute_tool(tool_name, arguments)
        except RETRYABLE_ERRORS as e:
            if attempt == max_retries - 1:
                raise
            # 指数退避 + 随机抖动
            delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
            time.sleep(delay)

纯指数退避可能导致多个客户端在同一时刻重试,加入0-1秒的随机抖动(Jitter)可以错开重试时间:

纯指数退避:1s, 2s, 4s, 8s(所有客户端相同)
加入抖动后:1.3s, 2.1s, 4.7s, 8.2s(每个客户端根据随机抖动不同)

8、如何设计Fallback机制?

Fallback是指当前工具不可用时,Agent自动切换到备选工具。

  • 工具降级

同一个功能准备多个备选工具:

python
TOOL_FALLBACK = {
    "search_web": ["search_web_v2", "search_bing"],
    "get_weather": ["get_weather_api2"],
}

def execute_with_fallback(tool_name, arguments):
    tools = [tool_name] + TOOL_FALLBACK.get(tool_name, [])
    for tool in tools:
        try:
            return execute_tool(tool, arguments)
        except Exception as e:
            logger.warning(f"工具{tool}调用失败,尝试其他工具: {e}")
            continue
    raise Exception(f"所有备选工具都失败: {tools}")

这里要注意首选工具和备选工具参数和用法可能不完全相同,在发生降级调用时,要处理好这部分的逻辑。

  • 策略级Fallback

当工具调用全部失败时,降级到不需要工具的方案:

正常流程:用户问天气 → 调用天气API → 返回实时天气 降级流程:天气API调用失败 → 返回给用户"当前无法获取实时天气,建议您查看天气App"

  • 缓存Fallback

当调用工具失败之后,尝试使用缓存数据:

python
def execute_with_cache_fallback(tool_name, arguments):
    # 先尝试实时调用
    try:
        result = execute_tool(tool_name, arguments)
        cache.set(tool_name, arguments, result, ttl=300)  # 缓存5分钟
        return result
    except Exception:
        # 实时调用失败,尝试从缓存获取
        cached = cache.get(tool_name, arguments)
        if cached:
            return {"data": cached, "source": "cache", "warning": "使用了缓存数据"}
        raise

使用缓存进行降级处理时,要结合具体业务场景,有些信息是必须要实时获取才有意义,避免获取到过期的信息。

9、如何避免Agent误调用工具?

Agent有时候会在不需要调用工具时调用工具,或者调用错误的工具。

  • Prompt中明确工具使用场景
工具使用规则:
- 只有当用户明确要求执行操作时才调用工具
- 如果用户只是在闲聊或问概念性问题,直接回答,不要调用工具
- 不确定是否需要调用工具时,优先不调用
  • 高风险的工具调用进行人工确认

如下示例使用LangGraph来进行工具调用确认:

python
from typing import TypedDict

from langgraph.graph import StateGraph, START, END
from langgraph.types import interrupt, Command
from langgraph.checkpoint.memory import InMemorySaver


class State(TypedDict):
    result: str


# 高风险工具
def delete_database():
    return "数据库已删除"


# 人工确认
def human_review(state: State):
    approved = interrupt("是否允许删除数据库?")
    return {"approved": approved}


# 根据审批结果路由
def router(state):
    return "tool" if state["approved"] else "reject"


# 执行工具
def tool(state):
    return {"result": delete_database()}


# 拒绝执行
def reject(state):
    return {"result": "已取消操作"}


builder = StateGraph(State)
builder.add_node("review", human_review)
builder.add_node("tool", tool)
builder.add_node("reject", reject)

builder.add_edge(START, "review")
builder.add_conditional_edges("review", router)
builder.add_edge("tool", END)
builder.add_edge("reject", END)

graph = builder.compile(checkpointer=InMemorySaver())

人工确认

python
graph.invoke(Command(resume=True), config=config)
  • 工具描述中写明边界

如在编写发送邮件send_email时,添加描述:发送邮件。仅在用户明确要求发送邮件时调用,不要在用户只是询问邮件相关问题时调用。

  • 减少工具数量

工具越多,LLM选错的概率越大。定期清理不常用的工具,保持工具列表精简。

10、如何限制Agent调用危险工具?

某些工具(如删除数据、执行代码、发送邮件)具有不可逆的影响,需要严格限制。

  • 权限分级
python
TOOL_RISK_LEVEL = {
    "search_web": "low",       # 只读操作,风险低
    "send_email": "medium",    # 有副作用,中等风险
    "delete_file": "high",     # 不可逆,高风险
    "execute_code": "critical" # 可能造成严重后果
}

def check_tool_permission(tool_name, user_role):
    risk = TOOL_RISK_LEVEL.get(tool_name, "unknown")
    if risk == "critical":
        return user_role == "admin"
    if risk == "high":
        return user_role in ["admin", "operator"]
    return True
  • 二次确认

高风险操作必须经过人工确认,具体示例如上面所示的删除数据二次确认的案例。

  • 日志审计

记录所有高风险工具的调用日志,方便排查问题和回滚。

11、工具执行异常如何回滚?

当工具执行到一半失败了,是否需要回滚,需要根据工具具体进行判断,查询类工具通常不需要回滚;而创建订单、扣库存、删除数据等写操作,如果后续流程失败,就应该考虑补偿机制。

由于 Agent 会跨数据库、HTTP API、第三方服务等多个系统,通常无法依赖传统数据库事务,而是采用 Saga 或补偿事务模式,为每个关键工具设计对应的回滚操作。

  • 事务性设计

尽量让工具调用支持事务,失败时自动回滚:

python
def execute_with_transaction(operations):
    executed = []
    try:
        for op in operations:
            result = execute_tool(op["tool"], op["arguments"])
            executed.append({"tool": op["tool"], "result": result})
        return {"success": True, "results": executed}
    except Exception as e:
        # 逆序回滚已成功执行的操作
        for op in reversed(executed):
            rollback(op["tool"], op["result"])
        return {"success": False, "error": str(e)}
  • 补偿操作

如果不能回滚,设计补偿操作来撤销影响:

python
COMPENSATION_MAP = {
    "send_email": "send_recall_email",
    "create_order": "cancel_order",
    "transfer_money": "reverse_transfer",
}

def compensate(tool_name, result):
    compensation_tool = COMPENSATION_MAP.get(tool_name)
    if compensation_tool:
        execute_tool(compensation_tool, {"original_result": result})
  • 标记待确认

对于无法回滚的操作,先标记为"待确认"状态,等待后续处理。

python
def execute_with_pending(tool_name, arguments):
    result = execute_tool(tool_name, arguments)
    # 标记为待确认状态
    mark_as_pending(result["id"])
    return result

def confirm_execution(result_id):
    mark_as_confirmed(result_id)

12、如何记录工具调用日志?

完善的日志记录是排查问题和优化Agent的基础。

(1)日志内容

每次工具调用需要记录以下信息:

python
def log_tool_call(tool_name, arguments, result, duration, error=None):
    log_entry = {
        "timestamp": datetime.now().isoformat(),
        "tool_name": tool_name,
        "arguments": arguments,
        "result_summary": str(result)[:200],  # 只记录摘要,避免日志过大
        "duration_ms": duration,
        "success": error is None,
        "error": str(error) if error else None,
        "session_id": get_session_id(),
        "user_id": get_user_id()
    }
    logger.info(json.dumps(log_entry, ensure_ascii=False))

(2)敏感信息脱敏

日志中可能包含敏感信息,需要脱敏处理:

python
def mask_sensitive(arguments):
    masked = arguments.copy()
    sensitive_keys = ["password", "token", "card_number", "id_card"]
    for key in sensitive_keys:
        if key in masked:
            masked[key] = "***"
    return masked

(3)日志分级

python
# 普通工具调用 → INFO级别
logger.info(f"工具调用成功: {tool_name}")

# 工具调用失败 → WARNING级别
logger.warning(f"工具调用失败: {tool_name}, 错误: {error}")

# 高风险工具调用 → 单独记录审计日志
audit_logger.info(f"高风险操作: {tool_name}, 用户: {user_id}")

13、如何追踪工具调用链路?

一个Agent任务可能包含多次工具调用,需要把它们串联起来形成完整的调用链路。

(1)Trace ID

给每个Agent任务分配一个唯一的Trace ID,所有相关的工具调用都带上这个ID

python
import uuid

class ToolCallTracer:
    def __init__(self):
        self.trace_id = str(uuid.uuid4())
        self.spans = []

    def start_span(self, tool_name, arguments):
        span = {
            "trace_id": self.trace_id,
            "span_id": str(uuid.uuid4()),
            "tool_name": tool_name,
            "arguments": arguments,
            "start_time": time.time(),
            "status": "running"
        }
        self.spans.append(span)
        return span["span_id"]

    def end_span(self, span_id, result=None, error=None):
        for span in self.spans:
            if span["span_id"] == span_id:
                span["end_time"] = time.time()
                span["duration"] = span["end_time"] - span["start_time"]
                span["result"] = result
                span["error"] = error
                span["status"] = "success" if error is None else "error"
                break

(2)调用链路可视化

把追踪数据可视化,方便排查问题:

python
Trace: abc-123
├── [0.0s] search_web(query="AI新闻")
│   └── [2.3s] 成功,返回10条结果
├── [2.5s] summarize(text=搜索结果)
│   └── [5.1s] 成功,生成摘要
└── [5.3s] send_email(to="team@example.com", content=摘要)
    └── [6.8s] 成功,邮件已发送

总耗时:6.8秒

(3)关联LLM调用

除了工具调用,LLM的调用也需要追踪,形成完整的调用链路:

python
完整调用链路:
├── LLM调用 #1 → 决定调用search_web
├── Tool调用 search_web → 返回搜索结果
├── LLM调用 #2 → 决定调用summarize
├── Tool调用 summarize → 返回摘要
├── LLM调用 #3 → 决定调用send_email
├── Tool调用 send_email → 发送成功
└── LLM调用 #4 → 生成最终回答

总耗时:20.3秒

好啦,今天这期 工具调用 面试题就到这里。后面我会每周至少更新1期面试题系列,想看后续 AI Agent 进阶面试题】 的朋友,欢迎关注「大志说编程」!

觉得有用的话,转发给正在面试的小伙伴,咱们下期见~

最后更新于: